/*
  轻量级 WebSocket ↔ SerialPort 串口网桥。

  传输方式：
  - 客户端通过 WebSocket 连接（默认 ws://localhost:3323）
  - 客户端以 JSON（类似单次 RPC）的形式发送请求；服务端统一回复 { id, ok, payload }
  - 服务端还会推送无 id 的异步事件（数据/状态/错误）

  客户端请求（Client → Server）：
  - { id, type: 'list' }
      → 回复 payload: [{ path: string }]
  - { id, type: 'open', options: { path, baudRate, dataBits, stopBits, parity } }
      → 按参数打开串口；成功后推送 status=true
  - { id, type: 'close' }
      → 关闭当前串口；并推送 status=false
  - { id, type: 'write', payload: { mode: 'ascii'|'hex', data: string } }
      → 往已打开的串口写入数据；回复 payload 含 { written }

  服务端事件（Server → Client，无 id）：
  - { type: 'status', payload: { connected: boolean } }
  - { type: 'data', payload: string }             // 为便捷，按 utf8 文本推送
  - { type: 'error', payload: string }
*/

const http = require('http')
const WebSocket = require('ws')
const { SerialPort } = require('serialport')

// WebSocket 监听端口（可用环境变量 SERIAL_BRIDGE_PORT 覆盖）
const PORT = process.env.SERIAL_BRIDGE_PORT ? Number(process.env.SERIAL_BRIDGE_PORT) : 3323

// 单个 HTTP Server，供 'ws' 挂载 WebSocket 服务
const server = http.createServer()
const wss = new WebSocket.Server({ server })

// 全局共享的端口池：同一物理串口仅打开一次，多个客户端可同时订阅读取
// 结构：path => { port: SerialPort, subscribers: Set<ws>, lastOptions }
const sharedPorts = new Map()

function ensureSharedPort(path, options) {
  let entry = sharedPorts.get(path)
  if (entry && entry.port && entry.port.isOpen) {
    return entry
  }
  const opts = {
    path: String(path),
    baudRate: Number(options && options.baudRate) || 115200,
    dataBits: Number(options && options.dataBits) || 8,
    stopBits: Number(options && options.stopBits) || 1,
    parity: String((options && options.parity) || 'none'),
    autoOpen: false
  }
  const port = new SerialPort(opts)
  entry = { port, subscribers: new Set(), lastOptions: opts }
  sharedPorts.set(path, entry)
  // 事件绑定仅一次，广播给订阅者
  port.on('data', (buf) => {
    const text = buf.toString('utf8')
    for (const client of entry.subscribers) {
      send(client, { type: 'data', payload: text })
    }
  })
  port.on('error', (e) => {
    const msg = String(e && e.message ? e.message : e)
    for (const client of entry.subscribers) {
      send(client, { type: 'error', payload: msg })
    }
  })
  return entry
}

async function openShared(path, options) {
  const entry = ensureSharedPort(path, options)
  if (!entry.port.isOpen) {
    await new Promise((resolve, reject) => entry.port.open((err) => err ? reject(err) : resolve()))
  }
  return entry
}

async function closeSharedIfNoSubscribers(path) {
  const entry = sharedPorts.get(path)
  if (!entry) return
  if (entry.subscribers.size === 0 && entry.port && entry.port.isOpen) {
    await new Promise((resolve) => entry.port.close(() => resolve()))
  }
}

// 安全发送 JSON（若对端已关闭连接，忽略发送错误）
function send(ws, obj) {
  try { ws.send(JSON.stringify(obj)) } catch (_) {}
}

wss.on('connection', (ws) => {
  // 当前会话订阅的端口 path（共享模式）
  let subscribedPath = null

  const pushStatus = (connected) => send(ws, { type: 'status', payload: { connected } })
  const pushError = (err) => send(ws, { type: 'error', payload: String(err && err.message ? err.message : err) })

  ws.on('message', async (msg) => {
    let req
    // 解析入站 JSON；若无效直接报错
    try { req = JSON.parse(String(msg)) } catch (e) { return pushError('Bad JSON') }
    const { id, type } = req || {}

    const reply = (ok, payload) => send(ws, { id, ok, payload })

    try {
      if (type === 'list') {
        // 枚举主机上的可用串口
        const ports = await SerialPort.list()
        return reply(true, ports.map(p => ({ path: p.path })))
      }

      if (type === 'open') {
        const opts = req.options || {}
        const path = String(opts.path)
        // 打开共享端口并订阅
        const entry = await openShared(path, opts)
        entry.subscribers.add(ws)
        subscribedPath = path
        pushStatus(true)
        return reply(true, { opened: true })
      }

      if (type === 'close') {
        // 取消订阅；若无订阅者则关闭物理端口
        if (!subscribedPath) return reply(true, { closed: true })
        const entry = sharedPorts.get(subscribedPath)
        if (entry) {
          entry.subscribers.delete(ws)
          await closeSharedIfNoSubscribers(subscribedPath)
        }
        subscribedPath = null
        pushStatus(false)
        return reply(true, { closed: true })
      }

      if (type === 'write') {
        // 校验连接状态并按模式编码要写入的数据
        if (!subscribedPath) return reply(false, 'port not open')
        const entry = sharedPorts.get(subscribedPath)
        if (!entry || !entry.port || !entry.port.isOpen) return reply(false, 'port not open')
        const payload = req.payload || {}
        const mode = payload.mode === 'hex' ? 'hex' : 'ascii'
        let out
        if (mode === 'hex') {
          const cleaned = String(payload.data || '').replace(/[^0-9a-fA-F]/g, '')
          out = Buffer.from(cleaned, 'hex')
        } else {
          out = Buffer.from(String(payload.data || ''), 'utf8')
        }
        await new Promise((resolve, reject) => entry.port.write(out, (err) => err ? reject(err) : resolve()))
        return reply(true, { written: out.length })
      }

      return reply(false, 'unknown type')
    } catch (e) {
      // 协议内错误：回复给请求方 + 异步事件便于前端记录
      pushError(e)
      return reply(false, String(e && e.message ? e.message : e))
    }
  })

  ws.on('close', async () => {
    // WebSocket 关闭时清理本会话的串口订阅
    if (subscribedPath) {
      const entry = sharedPorts.get(subscribedPath)
      if (entry) {
        entry.subscribers.delete(ws)
        await closeSharedIfNoSubscribers(subscribedPath)
      }
    }
  })
})

server.listen(PORT, () => {
  // eslint-disable-next-line no-console
  console.log(`[serial-bridge] listening on ws://localhost:${PORT}`)
})


